import {Injectable} from '@angular/core';
import {SubjectService} from '@core/services/events/subject.service';
import {UtilsService} from '@core/services/expression/utils.service';
import {Events} from "@core/services/events/Events";

@Injectable()
export class ExpressionService {

  private expressionToFunctionCache = <any>{};

  constructor(protected api: UtilsService, protected subject: SubjectService, protected events: Events) {
  }

  /**
   * 功能：表达式计算
   * @param {string} expression
   * @param params
   * @returns {any}
   */
  public eval(expression: string, params: any, isNotReturn: boolean = false): any {
    const result = this.evaluate(expression, params, isNotReturn);
    if (result === 'true' || result === 'false') {
      return JSON.parse(result);
    }
    return result;
  }

  /**
   * 功能：表达式计算
   * @param {Function | string} expressionOrFunc
   * @param params
   * @returns {any}
   */
  // tslint:disable-next-line:ban-types
  public evaluate(expressionOrFunc: Function | string, params: any, isNotReturn: boolean = false): any {
    params.api = this.api;
    params.subject = this.subject;
    params.events = this.events;
    if (typeof expressionOrFunc === 'function') {
      // valueGetter is a function, so just call it
      // tslint:disable-next-line:ban-types
      const func = <Function>expressionOrFunc;
      return func(params);
    } else if (typeof expressionOrFunc === 'string') {
      // valueGetter is an expression, so execute the expression
      const expression = <string>expressionOrFunc;
      return this.evaluateExpression(expression, params, isNotReturn);
    } else {
      console.error('apes: value should be either a string or a function', expressionOrFunc);
    }
  }

  private evaluateExpression(expression: string, params: any, isNotReturn: boolean = false): any {
    try {
      const javaScriptFunction = this.createExpressionFunction(expression, isNotReturn);
      // the params don't have all these values, rather we add every possible
      // value a params can have, which makes whatever is in the params available.
      const result = javaScriptFunction(params.context, params.field, params.form, params.model, params.node, params.data, params.value, params.oldValue, params.api, params.subject, params.source, params.events);
      return result;
    } catch (e) {
      // the expression failed, which can happen, as it's the client that
      // provides the expression. so print a nice message
      console.log('Processing of the expression failed. Expression = [' + expression + ']; Exception = [' + e + ']');
      return null;
    }
  }

  private createExpressionFunction(expression: any, isNotReturn: boolean = false) {
    // check cache first
    if (this.expressionToFunctionCache[expression]) {
      return this.expressionToFunctionCache[expression];
    }
    // if not found in cache, return the function
    const functionBody = this.createFunctionBody(expression, isNotReturn);
    const theFunction = new Function('ctx, field, form, model, node, data, value, oldValue, api, subject, source, events', functionBody);

    // store in cache
    this.expressionToFunctionCache[expression] = theFunction;

    return theFunction;
  }

  private createFunctionBody(expression: any, isNotReturn: boolean = false) {
    // if the expression has the 'return' word in it, then use as is,
    // if not, then wrap it with return and ';' to make a function
    if (expression.indexOf('return') >= 0 || isNotReturn) {
      return expression;
    } else {
      return 'return ' + expression + ';';
    }
  }

}

